package pers.chao.tool.aspect;

import cn.hutool.core.util.StrUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.springframework.aop.framework.AopContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;
import pers.chao.tool.infrastructure.log.BaseLogFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * Description: AOP获取注解工具、普通获取注解工具
 *
 * @author W.Chao
 * @date 2020/11/7 1:24
 **/
public final class AopUtil {

	private AopUtil() {
	}

	/**
	 * 1.获取method上的annClass注解
	 * 2.当1.获取为null时，获取类targetClass上最匹配的方法上的注解annClass
	 *
	 * @param targetClass
	 * @param method
	 * @param annClass    注解class
	 * @param <T>
	 * @return
	 */
	public static <T extends Annotation> T findMethodAnnotation(Class targetClass, Method method, Class<T> annClass) {
		Method m = method;
		T annotation = AnnotationUtils.findAnnotation(m, annClass);
		if (annotation != null) {
			return annotation;
		}
		Method mostSpecificMethod = ClassUtils.getMostSpecificMethod(m, targetClass);
		annotation = AnnotationUtils.findAnnotation(mostSpecificMethod, annClass);
		return annotation;
	}

	/**
	 * 获取类targetClass上的注解annClass
	 *
	 * @param targetClass
	 * @param annClass
	 * @param <T>
	 * @return
	 */
	public static <T extends Annotation> T findAnnotation(Class targetClass, Class<T> annClass) {
		return AnnotationUtils.findAnnotation(targetClass, annClass);
	}

	/**
	 * 获取方法上的注解
	 *
	 * @param method
	 * @param annClass
	 * @param <T>
	 * @return
	 */
	public static <T extends Annotation> T findAnnotation(Method method, Class<T> annClass) {
		return AnnotationUtils.findAnnotation(method, annClass);
	}

	/**
	 * 1.获取类方法上的注解
	 * 2.当1.中获取为null时，获取类上的该注解
	 *
	 * @param targetClass
	 * @param method
	 * @param annClass
	 * @param <T>
	 * @return
	 */
	public static <T extends Annotation> T findAnnotation(Class targetClass, Method method, Class<T> annClass) {
		T methodAnnotation = findMethodAnnotation(targetClass, method, annClass);
		if (methodAnnotation != null) {
			return methodAnnotation;
		}
		return findAnnotation(targetClass, annClass);
	}

	/**
	 * 获取类方法上的注解，方法上没有该注解时获取类上的该注解
	 *
	 * @param joinPoint
	 * @param annClass
	 * @param <T>
	 * @return
	 */
	public static <T extends Annotation> T findAnnotation(JoinPoint joinPoint, Class<T> annClass) {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		Method method = signature.getMethod();
		Class<?> targetClass = joinPoint.getTarget().getClass();
		return findAnnotation(targetClass, method, annClass);
	}

	/**
	 * 获取注解上的注解
	 *
	 * @param targetClass
	 * @param annClass
	 * @param targetAnnotation
	 * @param <T>
	 * @param <U>
	 * @return
	 */
	public static <T extends Annotation, U extends Annotation> U findAnnotatedAnnotation(Class targetClass, Class<T> annClass, Class<U> targetAnnotation) {
		if (!annClass.isAnnotation() || !targetAnnotation.isAnnotation()) {
			return null;
		}
		return findAnnotation(targetClass, annClass).annotationType().getAnnotation(targetAnnotation);
	}

	private static final Logger logger = BaseLogFactory.getLogger(AopUtil.class);

	/**
	 * 获取注解上的注解
	 *
	 * @param method
	 * @param annClass
	 * @param targetAnnotation
	 * @param <T>
	 * @param <U>
	 * @return
	 */
	public static <T extends Annotation, U extends Annotation> U findAnnotatedAnnotation(Method method, Class<T> annClass, Class<U> targetAnnotation) {
		if (!annClass.isAnnotation() || !targetAnnotation.isAnnotation()) {
			return null;
		}
		return findAnnotation(method, annClass).annotationType().getAnnotation(targetAnnotation);
	}

	/**
	 * 获取方法签名
	 *
	 * @param joinPoint
	 * @return
	 */
	public static String getMethodSignature(JoinPoint joinPoint) {
		StringBuilder methodName = new StringBuilder(joinPoint.getSignature().getName()).append("(");
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		String[] parameterNames = signature.getParameterNames();
		Class[] parameterTypes = signature.getParameterTypes();
		for (int i = 0; i < parameterTypes.length; i++) {
			if (i != 0) {
				methodName.append(",");
			}
			methodName.append(parameterTypes[i].getSimpleName()).append(" ").append(parameterNames[i]);
		}
		return methodName.append(")").toString();
	}

	/**
	 * 按照方法参数的定义顺序返回参数列表
	 *
	 * @param joinPoint
	 * @return
	 */
	public static Map<String, Object> getMethodArgsMap(JoinPoint joinPoint) {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		Map<String, Object> args = new LinkedHashMap<>();
		String[] parameterNames = signature.getParameterNames();
		for (int i = 0, len = parameterNames.length; i < len; i++) {
			args.put(parameterNames[i], joinPoint.getArgs()[i]);
		}
		return args;
	}

	/**
	 * 返回参数列表的参数值
	 *
	 * @param joinPoint
	 * @return
	 */
	public static List<Object> getMethodArgList(JoinPoint joinPoint) {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		List<Object> args = new LinkedList<>();
		String[] parameterNames = signature.getParameterNames();
		for (int i = 0, len = parameterNames.length; i < len; i++) {
			args.add(joinPoint.getArgs()[i]);
		}
		return args;
	}

	/**
	 * 返回参数列表的参数值
	 *
	 * @param joinPoint
	 * @return
	 */
	public static Object[] getMethodArgs(JoinPoint joinPoint) {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		String[] parameterNames = signature.getParameterNames();
		Object[] args = new Object[parameterNames.length];
		for (int i = 0, len = parameterNames.length; i < len; i++) {
			args[i] = joinPoint.getArgs()[i];
		}
		return args;
	}

	/**
	 * 将给定的数组组合为参数列表
	 *
	 * @param argNames
	 * @param args
	 * @return
	 */
	public static Map<String, Object> getMethodArgsMap(String[] argNames, Object[] args) {
		if (argNames.length != args.length) {
			return null;
		}
		Map<String, Object> argMap = new LinkedHashMap<>();
		for (int i = 0, len = argNames.length; i < len; i++) {
			argMap.put(argNames[i], args[i]);
		}
		return argMap;
	}

	/**
	 * 获取占位符指代的真实值，最多支持一个对象下的内置属性
	 *
	 * @param args 方法参数列表
	 * @param key  参数名
	 * @return
	 */
	public static Object replaceEscapedFrom(Map<String, Object> args, String key) {
		if (args == null || args.size() == 0) {
			return null;
		}
		if (StrUtil.startWith(key, "#", false)) {
			String arg = key.substring(1);
			if (!key.contains(".")) {
				return args.getOrDefault(arg, null);
			} else {
				// 有'.'字符表示获取对象中的属性
				String[] split = arg.split(".");
				Object obj = args.get(split[0]);
				String propertyName = split[1];
				// 反射获取属性
				try {
					//得到class
					Class clazz = obj.getClass();
					//得到所有属性
					Field[] fields = clazz.getDeclaredFields();
					for (Field field : fields) {
						//打开私有访问
						field.setAccessible(true);
						String name = field.getName();
						if (propertyName.equals(name)) {
							return field.get(obj);
						}
					}
				} catch (IllegalAccessException e) {
					throw new AopException("get value from parameter maps error, can not find key: {}", key, e);
				}
			}
		}
		return key;
	}

	/**
	 * Description: 解决切面类C的A方法调用B方法时切面失效问题
	 * <p>
	 * 食用方法：
	 * 1.依赖：spring-boot-starter-aop
	 * 2.@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
	 * <p>
	 * <p>
	 * 获取aop当前切面类
	 *
	 * @param clazz
	 * @param <T>
	 * @return
	 */
	@SuppressWarnings("all")
	public static <T> T getCurrentAopProxy(Class<T> clazz) {
		return (T) AopContext.currentProxy();
	}
}
